FreeCore
Library
Parameterized Functions - Advanced
Features
Learn how to master the advanced features of
parameterized function programming
by
Rune Baeverrud
In this tutorial we will investigate some of the more advanced features of parameterized function programming. I strongly recommend that you first read my tutorial Parameterized Functions Made Simple - there are some concepts covered there that you may find very useful, even if you already have been designing parameterized functions for a while.
We will first spend a section on investigating the LOG2( ) compile-time function which is extremely useful in parameterized function programming. Next, we will continue on to investigate some useful AHDL constructs like IF-GENERATE, FOR-GENERATE, ASSERT and USED. Lastly, we will take a quick look at the predefined DEVICE_FAMILY parameter.
For this tutorial, we will continue to build on the frequency divider function created in the first tutorial.
In the parameterized example provided in the first tutorial, it should not be necessary to specify the WIDTH parameter. Given the value of the DIVIDE parameter, the number of bits required to represent DIVIDE could be calculated. There is a nifty way to do this calculation, by using the built-in LOG2( ) (logarithm base2) function. Read on...
Ask yourself: How many bits are required to hold a number with N possible values? The answer to this question is exactly what the LOG2( ) function gives us. See the table below. If you want to hold a number with 5 possible values, you would require 2.3219 bits. Of course, this is a mathematical view, so we would always round the LOG2( ) result up to the nearest whole number. The upwards rounding is what the CEIL function will do for us.
N | LOG2(N) | CEIL ( LOG2(N) ) |
3 | 1.5850 | 2 |
4 | 2.0000 | 2 |
5 | 2.3219 | 3 |
15 | 3.9069 | 4 |
16 | 4.0000 | 4 |
17 | 4.0875 | 5 |
255 | 7.9944 | 8 |
256 | 8.0000 | 8 |
257 | 8.0056 | 9 |
How many bits do we need to represent the number 255? Remember that if you can represent the number 255, the total number of possible values would be 256, counting the value of zero as well. Therefore, the number of bits required for holding any number N would always be:
CEIL ( LOG2 (N+1) ) |
We now have the knowledge to simplify the frequency divider function even further, by removing the WIDTH parameter. Remember from our example that with a DIVISOR value of 248, the highest value assigned to the Counter[] variable is (DIVIDE - 1) = 247. Therefore, in this case (N+1) equals to (247+1) = DIVIDE.
PARAMETERS ( DIVIDE = 248 ); CONSTANT DIV_TMP = (DIVIDE - 1); CONSTANT WIDTH = CEIL ( LOG2(DIVIDE) ); SUBDESIGN div_5 ( SysClk : INPUT; Enable : INPUT = VCC; Count[WIDTH-1..0], Zero : OUTPUT; ) VARIABLE Counter[WIDTH-1..0] : DFF; BEGIN Counter[].clk = SysClk; Count[] = Counter[]; IF Enable THEN IF Counter[] == 0 THEN Counter[] = DIV_TMP; Zero = VCC; ELSE Counter[] = Counter[] - 1; END IF; ELSE Counter[] = Counter[]; END IF; END; |
The div function suffers from glitches on the Zero output. Let’s add another parameter, specifying that I would like to optionally register the Zero output. By using IF-GENERATE, I can optionally declare and use an extra register for the Zero output. This is how to do it:
PARAMETERS ( DIVIDE = 248, GLITCH_FREE = "YES" ); CONSTANT DIV_TMP = (DIVIDE - 1); CONSTANT WIDTH = CEIL ( LOG2(DIVIDE) ); SUBDESIGN div_6 ( SysClk : INPUT; Enable : INPUT = VCC; Count[WIDTH-1..0], Zero : OUTPUT; ) VARIABLE Counter[WIDTH-1..0] : DFF; IF GLITCH_FREE == "YES" GENERATE ZeroReg: DFF; ELSE GENERATE ZeroNode: NODE; END GENERATE; BEGIN Counter[].clk = SysClk; Count[] = Counter[]; IF GLITCH_FREE == "YES" GENERATE Zero = ZeroReg; ZeroReg.clk = SysClk; ELSE GENERATE Zero = ZeroNode; END GENERATE; IF Enable THEN IF Counter[] == 0 THEN Counter[] = DIV_TMP; IF GLITCH_FREE == "YES" GENERATE ZeroReg = VCC; ELSE GENERATE ZeroNode = VCC; END GENERATE; ELSE Counter[] = Counter[] - 1; END IF; ELSE Counter[] = Counter[]; END IF; END; |
You can see that the IF-GENERATE statement can be used in the VARIABLE section of the file as well as in the logic section. Note: In this example – when GLITCH_FREE is set to "YES", the Zero output will be delayed by one SysClk period because of the extra register inserted.
With the ASSERT statement, you can check for the validity of compile-time values like parameters, constants or evaluated expressions, and write messages to the compiler message window. Let’s add some parameter checking to the above file. This is what we would like to check for:
PARAMETERS ( DIVIDE = 248, GLITCH_FREE = "YES" ); CONSTANT DIV_TMP = (DIVIDE - 1); CONSTANT WIDTH = CEIL ( LOG2(DIVIDE) ); SUBDESIGN div_7 ( SysClk : INPUT; Enable : INPUT = VCC; Count[WIDTH-1..0], Zero : OUTPUT; ) VARIABLE Counter[WIDTH-1..0] : DFF; IF GLITCH_FREE == "YES" GENERATE ZeroReg: DFF; ELSE GENERATE ZeroNode: NODE; END GENERATE; BEGIN ASSERT (DIVIDE >= 3) REPORT "DIVIDE parameter must be equal to or larger than 3. It is now %" DIVIDE SEVERITY ERROR; ASSERT (GLITCH_FREE == "YES" OR GLITCH_FREE == "NO") REPORT "GLITCH_FREE parameter should be ""YES"" or ""NO"". It is now ""%"". Compiling for GLITCH_FREE==""NO""" GLITCH_FREE SEVERITY WARNING; Counter[].clk = SysClk; Count[] = Counter[]; IF GLITCH_FREE == "YES" GENERATE Zero = ZeroReg; ZeroReg.clk = SysClk; ELSE GENERATE Zero = ZeroNode; END GENERATE; IF Enable THEN IF Counter[] == 0 THEN Counter[] = DIV_TMP; IF GLITCH_FREE == "YES" GENERATE ZeroReg = VCC; ELSE GENERATE ZeroNode = VCC; END GENERATE; ELSE Counter[] = Counter[] - 1; END IF; ELSE Counter[] = Counter[]; END IF; END; |
The ASSERT statement is executed if the test condition is false. The ’%’ character in the REPORT statement is a placeholder for parameter values that you would like to display as part of the message. The parameter names are given directly after the REPORT statement, separated by commas if more than one.
With the SEVERITY statement, you have the options to specify ERROR, WARNING or INFO. If you specify ERROR, the compilation will be aborted.
You may also place ASSERT statements in the VARIABLE section of the AHDL file, which can be very useful for checking validity of bus indexes before declaring variables. For example, you would like to abort compilation if you tried to declare a variable like Counter[WIDTH-1..0] if the parameter WIDTH is set to 0, 1 or maybe even "YES". In this particular case, the compilation would probably be aborted anyway, but sometimes you would like to use the ASSERT statement in this way to provide a more meaningful message.
You may also issue unconditional INFO messages by using the following form:
ASSERT REPORT "Dear Rune, How are you today?" SEVERITY INFO |
The FOR-GENERATE is very useful for creating repetitive structures. For our continued example, I will use FOR-GENERATE to reverse the bits on the Count[] output. It is possible to simply replace
Count[] = Counter[] with Count[0..WIDTH-1] = Counter[WIDTH-1..0] |
but this will cause a warning message during compilation saying that I may have unintentionally reversed my bits. To avoid the warning message, you could use the FOR-GENERATE statement like this:
PARAMETERS ( DIVIDE = 248, GLITCH_FREE = "YES", REVERSE_OUTPUT = "NO" ); CONSTANT DIV_TMP = (DIVIDE - 1); CONSTANT WIDTH = CEIL ( LOG2(DIVIDE) ); SUBDESIGN div_8 ( SysClk : INPUT; Enable : INPUT = VCC; Count[WIDTH-1..0], Zero : OUTPUT; ) VARIABLE Counter[WIDTH-1..0] : DFF; IF GLITCH_FREE == "YES" GENERATE ZeroReg: DFF; ELSE GENERATE ZeroNode: NODE; END GENERATE; BEGIN ASSERT (DIVIDE >= 3) REPORT "DIVIDE parameter must be equal to or larger than 3. It is now %" DIVIDE SEVERITY ERROR; ASSERT (GLITCH_FREE == "YES" OR GLITCH_FREE == "NO") REPORT "GLITCH_FREE parameter should be ""YES"" or ""NO"". It is now ""%"". Compiling for GLITCH_FREE==""NO""" GLITCH_FREE SEVERITY WARNING; ASSERT (REVERSE_OUTPUT == "YES" OR REVERSE_OUTPUT == "NO") REPORT "REVERSE_OUTPUT parameter should be ""YES"" or ""NO"". It is now ""%"". Compiling for REVERSE_OUTPUT==""NO""" GLITCH_FREE SEVERITY WARNING; Counter[].clk = SysClk; IF REVERSE_OUTPUT == "YES" GENERATE FOR i IN 0 TO (WIDTH-1) GENERATE Count[i] = Counter[(WIDTH-1) - i]; END GENERATE; ELSE GENERATE Count[] = Counter[]; END GENERATE; IF GLITCH_FREE == "YES" GENERATE Zero = ZeroReg; ZeroReg.clk = SysClk; ELSE GENERATE Zero = ZeroNode; END GENERATE; IF Enable THEN IF Counter[] == 0 THEN Counter[] = DIV_TMP; IF GLITCH_FREE == "YES" GENERATE ZeroReg = VCC; ELSE GENERATE ZeroNode = VCC; END GENERATE; ELSE Counter[] = Counter[] - 1; END IF; ELSE Counter[] = Counter[]; END IF; END; |
By using the USED( ) compile-time function you can check for unused ports and take appropriate actions. Please note that the USED function cannot replace default values on the input ports.
PARAMETERS ( DIVIDE = 248, GLITCH_FREE = "YES", REVERSE_OUTPUT = "NO" ); CONSTANT DIV_TMP = (DIVIDE - 1); CONSTANT WIDTH = CEIL ( LOG2(DIVIDE) ); SUBDESIGN div_9 ( SysClk : INPUT; Enable : INPUT = VCC; Count[WIDTH-1..0], Zero : OUTPUT; ) VARIABLE Counter[WIDTH-1..0] : DFF; IF GLITCH_FREE == "YES" GENERATE ZeroReg: DFF; ELSE GENERATE ZeroNode: NODE; END GENERATE; BEGIN ASSERT (DIVIDE >= 3) REPORT "DIVIDE parameter must be equal to or larger than 3. It is now %" DIVIDE SEVERITY ERROR; ASSERT (GLITCH_FREE == "YES" OR GLITCH_FREE == "NO") REPORT "GLITCH_FREE parameter should be ""YES"" or ""NO"". It is now ""%"". Compiling for GLITCH_FREE==""NO""" GLITCH_FREE SEVERITY WARNING; ASSERT (REVERSE_OUTPUT == "YES" OR REVERSE_OUTPUT == "NO") REPORT "REVERSE_OUTPUT parameter should be ""YES"" or ""NO"". It is now ""%"". Compiling for REVERSE_OUTPUT==""NO""" GLITCH_FREE SEVERITY WARNING; Counter[].clk = SysClk; IF REVERSE_OUTPUT == "YES" GENERATE FOR i IN 0 TO (WIDTH-1) GENERATE Count[i] = Counter[(WIDTH-1) - i]; END GENERATE; ELSE GENERATE Count[] = Counter[]; END GENERATE; IF GLITCH_FREE == "YES" GENERATE Zero = ZeroReg; ZeroReg.clk = SysClk; ELSE GENERATE Zero = ZeroNode; END GENERATE; IF USED (Enable) GENERATE ASSERT REPORT "You have decided to use the Enable input. Thank you for your support." SEVERITY INFO; ELSE GENERATE ASSERT REPORT "You have decided NOT to use the Enable input. Please reconsider." SEVERITY INFO; END GENERATE; IF Enable THEN IF Counter[] == 0 THEN Counter[] = DIV_TMP; IF GLITCH_FREE == "YES" GENERATE ZeroReg = VCC; ELSE GENERATE ZeroNode = VCC; END GENERATE; ELSE Counter[] = Counter[] - 1; END IF; ELSE Counter[] = Counter[]; END IF; END; |
There is a predefined parameter in the MAX+PLUS II environment called DEVICE_FAMILY. To use it, you need to declare it in the parameters section of your parameterized file, like this:
PARAMETERS ( DIVIDE = 248, GLITCH_FREE = "YES", REVERSE_OUTPUT = "NO", DEVICE_FAMILY ); |
You can now perform conditional compilation using the DEVICE_FAMILY parameter. For example, you may want to implement a function in two different ways, depending on whether you use a MAX 7000 device or a FLEX 10K device. Also, if you want to, you could simply output the value of the DEVICE_FAMILY parameter:
ASSERT REPORT "Compiling for % device family" DEVICE_FAMILY SEVERITY INFO; |
The DEVICE_FAMILY parameter may take on one of the following values, depending on which device you are compiling for: FLEX10KA, FLEX10K, FLEX8000, FLEX6000, MAX9000, MAX7000S, MAX7000E, MAX7000, MAX5000, CLASSIC.
Last updated 08 Feb 2001 12:10